package cn.com.zero.file.client;

import cn.com.zero.base.util.SpringApplicationUtil;
import cn.com.zero.file.config.properties.FileUploadProperties;
import cn.com.zero.file.util.FileUtil;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.common.utils.BinaryUtil;
import com.aliyun.oss.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.Formatter;

/**
 * @author liuxh
 * @version 1.0.0
 * @Description 阿里云对象存储Template
 * @createTime 2023/2/1 17:40
 */
public class OSSTemplate {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    private FileUploadProperties fileUploadProperties;
    private String bucketName;

    // 在@Configuration构建本对象的类需要有一个默认无参构造器
    public OSSTemplate() {
    }

    public OSSTemplate(FileUploadProperties fileUploadProperties, String bucketName) {
        this.fileUploadProperties = fileUploadProperties;
        this.bucketName = bucketName;
    }

    /**
     * 创建存储空间
     *
     * @param bucketName 存储空间名称
     */
    private void createBucket(String bucketName, CannedAccessControlList cannedAccessControlList) {
        OSS ossClient = this.getOssClient();
        try {
            // 创建存储空间
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            // 存储类型为标准存储
            createBucketRequest.setStorageClass(StorageClass.Standard);
            // 数据容灾类型默认为本地冗余存储，即DataRedundancyType.LRS。
            createBucketRequest.setDataRedundancyType(DataRedundancyType.LRS);
            if (cannedAccessControlList != null) {
                // 设置存储空间读写权限为公共读，默认为私有。
                createBucketRequest.setCannedACL(cannedAccessControlList);
            }
            ossClient.createBucket(createBucketRequest);

        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    /**
     * 判断存储空间是否存在
     *
     * @param bucketName 存储空间名称
     * @return 是否存在 true： 是 false：否
     */
    public boolean bucketExists(String bucketName) {
        OSS ossClient = this.getOssClient();
        try {
            return ossClient.doesBucketExist(bucketName);
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 上传文件到默认存储空间
     *
     * @param objectName              文件完整路径 比如：exampledir/exampleobject.txt
     * @param file                    文件
     * @param cannedAccessControlList 该文件的指定策略:<br/>
     *                                若为null，则为默认策略，即继承自bucket的策略
     *                                Private：私有 PublicRead：公共读 PublicReadWrite：公共读写 Default：默认
     */
    public void upload(String objectName, MultipartFile file, CannedAccessControlList cannedAccessControlList) {
        upload(this.bucketName, objectName, file, cannedAccessControlList);
    }


    /**
     * 上传文件到指定存储空间
     *
     * @param bucketName              存储空间
     * @param objectName              文件完整路径 比如：exampledir/exampleobject.txt
     * @param file                    文件
     * @param cannedAccessControlList 该文件的指定策略:<br/>
     *                                若为null，则为默认策略，即继承自bucket的策略
     *                                Private：私有 PublicRead：公共读 PublicReadWrite：公共读写 Default：默认
     */
    public void upload(String bucketName, String objectName, MultipartFile file, CannedAccessControlList cannedAccessControlList) {
        OSS ossClient = this.getOssClient();
        try {
            String extension = FileUtil.getExtension(file);
            if (!FileUtil.isAllowedExtension(extension, fileUploadProperties.getAllowedExtension())) {
                throw new RuntimeException("上传文件失败，不允许上传文件类型为[" + extension + "]的文件！");
            }
            ossClient.putObject(bucketName, objectName, file.getInputStream());
            // 设置文件权限
            if (cannedAccessControlList != null) {
                ossClient.setObjectAcl(bucketName, objectName, cannedAccessControlList);
            }
        } catch (IOException e) {
            log.error("【文件服务】获取文件输入流异常，异常信息,", e);
            throw new RuntimeException("获取文件输入流异常", e);
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 文件是否存在（默认存储空间）
     *
     * @param objectName 不包含Bucket名称在内的Object完整路径
     * @return 是否存在 true：是 false：否
     */
    public boolean ObjectExist(String objectName) {
        return ObjectExist(this.bucketName, objectName);
    }


    /**
     * 文件是否存在
     *
     * @param bucketName 存储空间
     * @param objectName 不包含Bucket名称在内的Object完整路径
     * @return 是否存在 true：是 false：否
     */
    public boolean ObjectExist(String bucketName, String objectName) {
        OSS ossClient = this.getOssClient();
        try {
            return ossClient.doesObjectExist(bucketName, objectName);
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }

    /**
     * 文件下载
     *
     * @param objectName Object完整路径
     * @return byte数组
     */
    public byte[] download(String objectName) {
        return download(this.bucketName, objectName);
    }

    /**
     * 文件下载
     *
     * @param bucketName 存储空间
     * @param objectName Object完整路径
     * @return byte数组
     */
    public byte[] download(String bucketName, String objectName) {
        OSS ossClient = this.getOssClient();
        try {
            OSSObject ossObject = ossClient.getObject(bucketName, objectName);
            // 读取文件内容。
            InputStream objectContent = ossObject.getObjectContent();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[1024];
            while ((nRead = objectContent.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            byte[] byteArray = buffer.toByteArray();

            // 数据读取完成后，获取的流必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
            objectContent.close();
            // ossObject对象使用完毕后必须关闭，否则会造成连接泄漏，导致请求无连接可用，程序无法正常工作。
            ossObject.close();
            return byteArray;
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (Throwable ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    /**
     * 删除文件
     *
     * @param objectName Object完整路径
     */
    public void deleteObject(String objectName) {
        deleteObject(this.bucketName, objectName);
    }

    /**
     * 删除文件
     *
     * @param bucketName 存储空间
     * @param objectName Object完整路径
     */
    public void deleteObject(String bucketName, String objectName) {
        OSS ossClient = this.getOssClient();
        try {
            ossClient.deleteObject(bucketName, objectName);
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    /**
     * 获取私有图片签名
     *
     * @param bucketName 存储空间
     * @param objectName Object完整路径
     * @param expireMin  签名失效时间，单位分
     * @param style      图片处理参数，可为null，
     *                   示例：  将图片缩放为固定宽高100 px后，再旋转90°： "image/resize,m_fixed,w_100,h_100/rotate,90"
     * @return 签名URL
     */
    public URL getPrivateImgPresignedUrl(String bucketName, String objectName, Integer expireMin, String style) {
        OSS ossClient = null;
        try {
            ossClient = this.getOssClient();
            Date expiration = new Date(new Date().getTime() + 1000 * 60L * expireMin);
            GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.GET);
            req.setExpiration(expiration);
            if (StringUtils.hasLength(style)) {
                req.setProcess(style);
            }
            return ossClient.generatePresignedUrl(req);
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    /**
     * 将指定存储空间中的图片按照比例压缩存储到另一个存储空间中
     *
     * @param bucketName       指定存储空间
     * @param objectName       指定Object完整路径
     * @param targetBucketName 目标存储空间
     * @param targetObjectName 目标Object完整路径
     * @param width            指定压缩后图片的宽
     * @param height           指定压缩后图片的高
     */
    public void compressImgBucket(String bucketName, String objectName, String targetBucketName, String targetObjectName, Integer width, Integer height) {
        OSS ossClient = null;
        try {
            ossClient = this.getOssClient();
            if (width == null || width <= 0 || height == null || height <= 0) {
                throw new RuntimeException("指定压缩图片的宽和高不能为空!");
            }
            // 将图片缩放为固定宽高
            StringBuilder sbStyle = new StringBuilder();
            Formatter styleFormatter = new Formatter(sbStyle);
            String styleType = "image/resize,m_fixed,w_" + width + ",h_" + height;
            // 将处理后的图片命名为targetObjectName并保存到targetBucketName
            styleFormatter.format("%s|sys/saveas,o_%s,b_%s", styleType,
                    BinaryUtil.toBase64String(targetObjectName.getBytes()),
                    BinaryUtil.toBase64String(targetBucketName.getBytes()));
            ProcessObjectRequest request = new ProcessObjectRequest(bucketName, objectName, sbStyle.toString());
            GenericResult processResult = ossClient.processObject(request);
            processResult.getResponse().getContent().close();
        } catch (OSSException oe) {
            log.error("【文件服务】阿里云存储服务器端异常，异常信息：{}", oe.getMessage());
            throw new RuntimeException("阿里云存储服务器端异常", oe);
        } catch (ClientException | IOException ce) {
            log.error("【文件服务】客户端向服务器发送请求以及数据传输时异常，异常信息：{}", ce.getMessage());
            throw new RuntimeException("客户端向服务器发送请求以及数据传输时异常", ce);
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }


    /**
     * 获取多例的客户端对象
     *
     * @return 客户端对象
     */
    public OSS getOssClient() {
        return SpringApplicationUtil.getBean(OSS.class);
    }

    /**
     * 获取文件上传配置信息
     *
     * @return 配置信息
     */
    public FileUploadProperties getFileUploadProperties() {
        return fileUploadProperties;
    }

    /**
     * 获取默认存储桶名称
     *
     * @return 默认存储桶名称
     */
    public String getBucketName() {
        return bucketName;
    }
}
